import axios from 'axios';
import mitt from "mitt";
import type { Axios, AxiosInstance, AxiosResponse, CreateAxiosDefaults } from 'axios';
import type { UserConfig, ApiConfig, UserRequestConfig } from '../type';
import repeatRequest from '../repeatRequest/repeatRequest'
import { useCache } from '../cache/response'
import { cloneDeep, has } from 'lodash-es'

type isRequestSuccessFn = (response: AxiosResponse) => boolean
type StartLoadingFn = (config: UserConfig) => any
type StopLoadingFn = (loadingInstance: any) => any
type InjectHeaderFn = (config: UserConfig) => any

const cacheData = useCache()
const userRequestConfigKeys: Array<string> = [
  'loading',
  'force',
  'cache',
  'cacheExpires',
  'isUpload',
  'allowRepeat',
  'pathData',
]
export default class AxiosWrapper <T extends string> {
  
  axios: AxiosInstance

  private emitter = mitt<{
    error: Error,
  }>()

  private apiMap: Record<string, ApiConfig>

  private isRequestSuccessFn: isRequestSuccessFn = (response: AxiosResponse) => {
    return has(response.data, 'code') && response.data.code === 0
  }

  private startLoadingFn: StartLoadingFn

  private stopLoadingFn: StopLoadingFn

  private injectHeaderFn: InjectHeaderFn

  private loadingInstance: any

  public interceptors: AxiosInstance['interceptors']

  constructor (config: CreateAxiosDefaults & { apiMap: Record<T, ApiConfig> }) {
    this.axios = axios.create(config)

    this.interceptors = this.axios.interceptors

    this.apiMap = config.apiMap
  }

  on (type, handler) {
    return this.emitter.on(type, handler)
  }

  off (...args) {
    return this.emitter.off.call(this.emitter, ...args)
  }

  emit (...args) {
    this.emitter.emit.call(this.emitter, ...args)
  }

  request (apiName: T, userConfig: UserConfig = {}) {
    const apiConfig = cloneDeep(this.apiMap[apiName]);

    if (userConfig.pathData) {
      apiConfig.config.url = this.getUrlWithPathData(apiConfig.config.url, userConfig.pathData)
    }

    const config = Object.assign({}, apiConfig.config, userConfig)

    return this.proxyRequest(config)
  }

  proxyRequest(config: UserConfig) {
    const requestKey = this.getRequestKey(config)

    if (repeatRequest.isRepeatRequest(requestKey) && !config.allowRepeat) {
      return repeatRequest.repeatRequestEnd(requestKey)
    }

    // 使用缓存，且不强制请求，且存在缓存数据，且缓存数据未过期
    // 则直接返回缓存数据
    if (config.cache && !config.force && cacheData.get(requestKey)&& !cacheData.isExpires(requestKey)) {
      return Promise.resolve(cacheData.get(requestKey))
    }

    if (config.loading && this.startLoadingFn) {
      this.loadingInstance = this.startLoadingFn(config)
    }

    if (this.injectHeaderFn) {
      config.headers = Object.assign(
        {},
        this.injectHeaderFn(config),
        config.headers || {}
      );
    }

    if (config.isUpload) {
      const rawData = config.data

      config.headers = config.headers
        ? Object.assign(config.headers, { 'Content-Type': 'multipart/form-data' })
        : { 'Content-Type': 'multipart/form-data' }
      
      config.data = new FormData()

      for (const key in rawData) {
        const val = rawData[key]
        if (val instanceof Array) {
          val.forEach(v => {
            config.data.append(key, v);
          });
        } else if (key) {
          config.data.append(key, val);
        }
      }
    }

    const requestInstance = this.axios(this.getPureConfig(config)).then((response) => {
      if (
        (response.data && this.isRequestSuccessFn(response) && config.cache) ||
        (response.data && config.cache)
      ) {
        // 保存到缓存
        const cache = typeof config.cache === 'boolean' ? 'memory' : config.cache
        cacheData.set(requestKey, response, cache, config.cacheExpires || 0)
      }

      repeatRequest.boradcastResolve(requestKey, response)

      return response
    }).catch((err) => {
      repeatRequest.boradcastReject(requestKey, err)

      return Promise.reject(err)
    }).finally(() => {
      if (this.loadingInstance && this.stopLoadingFn) {
        this.stopLoadingFn(this.loadingInstance)
        this.loadingInstance = null
      }
      repeatRequest.removeRecordRequest(requestKey)
    })

    repeatRequest.recordRequest({
      key: requestKey,
      lastReqTime: Date.now(),
      instance: requestInstance
    })

    return requestInstance
  }

  isRequestSuccess (fn: isRequestSuccessFn) {
    this.isRequestSuccessFn = fn
  }

  startLoading (fn: StartLoadingFn) {
    this.startLoadingFn = fn
  }

  stopLoading (fn: StopLoadingFn) {
    this.stopLoadingFn = fn
  }

  injectHeader (fn: InjectHeaderFn) {
    this.injectHeaderFn = fn
  }

  getRequestKey (config) {
    return config.url + JSON.stringify(config.params || {})
  }

  getUrlWithPathData (url, pathData) {
    return url.replace(/:(\w+)/g, function (allMatch: string, varName: string) {
      return pathData[varName]
    })
  }

  getPureConfig (config) {
    const pureConfig = Object.create({})

    for (const key in config) {
      if (!userRequestConfigKeys.includes(key)) {
        pureConfig[key] = config[key]
      }
    }

    return pureConfig
  }
}
